package cool.tianyou.blue.bird.module.synchronize.service;

import cn.hutool.core.map.MapBuilder;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import cool.tianyou.blue.bird.common.mapper.DeviceMapper;
import cool.tianyou.blue.bird.common.model.contstant.DeviceMessageEnum;
import cool.tianyou.blue.bird.common.model.po.Device;
import cool.tianyou.blue.bird.common.model.po.LoginUser;
import cool.tianyou.blue.bird.common.tool.LocalCache;
import cool.tianyou.blue.bird.common.util.JsonReturn;
import cool.tianyou.blue.bird.config.PrometheusCustomMonitor;
import cool.tianyou.blue.bird.model.NotificationMessage;
import cool.tianyou.blue.bird.model.SessionHolder;
import cool.tianyou.blue.bird.module.synchronize.model.PushResult;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.websocket.Session;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * webSocket服务
 *
 * @author 刘天佑
 * @date 2021-09-02
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class WebSocketService {

  private final DeviceMapper deviceMapper;
  private DeviceService deviceService;
  private final SessionHolder sessionHolder;
  private final LocalCache localCache;
  private static final String TOKEN = "token";
  private static final String DEVICE_ID = "deviceId";
  private final PrometheusCustomMonitor monitor;

  public void onOpen(Session session, String token, Integer deviceId) {
    Optional<LoginUser> loginUser = localCache.get(token);
    DeviceMessageEnum echo = DeviceMessageEnum.OFFLINE;
    if (loginUser.isPresent()) {
      sessionHolder.addSession(loginUser.get().getUserId(), deviceId, session, value -> {
        final Device device = deviceMapper.selectByPrimaryKey(value);
        if (device != null) {
          device.setOnline(false);
          deviceMapper.updateByPrimaryKey(device);
          deviceService.clearCache(device.getUserId());
        }
      });
      echo = DeviceMessageEnum.BEAT;
    }
    try {
      sendMessage(session, JsonReturn.ope().setMsg(echo.name()).toJson());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void sendMessage(Session session, String message) throws IOException {
    if (session != null && session.isOpen()) {
      synchronized (session) {
        session.getBasicRemote().sendText(message);
      }
    }
  }


  public PushResult sendMessage(LoginUser loginUser, Map<Integer, NotificationMessage> messages) {
    PushResult pushResult = new PushResult();
    messages.forEach((deviceId, message) -> {
      Optional<Session> session = sessionHolder.getSession(loginUser.getUserId(), deviceId);
      session.ifPresent(s -> {
        try {
          message.setFromDevice(new Device(loginUser));
          sendMessage(s, JSON.toJSONString(message));
          pushResult.add(getAlias(deviceId));
        } catch (IOException e) {
          e.printStackTrace();
        }
      });
    });
    monitor.messageCountAdd(pushResult.getAlias().size());
    return pushResult;
  }


  private String getAlias(Integer deviceId) {
    Device device = deviceMapper.selectByPrimaryKey(deviceId);
    return device == null ? null : device.getAlias();
  }

  /**
   * 发送消息给特定的设备
   *
   * @param userId  用户id
   * @param message 消息
   * @return 是否发送成功
   */
  public void sendMessageToSpecialDevice(Integer userId, Integer deviceId, Object message) {
    Optional<Session> session = sessionHolder.getSession(userId, deviceId);

    session.ifPresent(s -> {
      try {
        sendMessage(s, JSON.toJSONString(message));
      } catch (Exception e) {
        log.error("消息发送失败,目标:{}:", deviceId, e);
      }
    });
  }

  private Integer decodeJwtGetUserId(String token) {
    final DecodedJWT decode = JWT.decode(token);
    final List<String> audience = decode.getAudience();
    return Integer.parseInt(audience.get(0).split("#")[0]);

  }

  public void onMessage(Session session, String message) {
    log.info("客户端消息:{}", message);
    final Optional<LoginUser> loginUser = getLoginUser(session);
    final Integer userId = decodeJwtGetUserId(session.getPathParameters().get(TOKEN));
    final Integer deviceId = Integer.valueOf(session.getPathParameters().get(DEVICE_ID));
    sessionHolder.heartBeat(userId, deviceId);
    if (!loginUser.isPresent()) {
      notifyOffline(session, deviceId, userId);
      return;
    }
    if (deviceService == null) {
      deviceService = SpringUtil.getBean(DeviceService.class);
    }
    Optional<Device> first = deviceService.getDeviceByUserId(userId).stream()
        .filter(device -> Objects.equals(device.getId(), deviceId)).findFirst();
    first.ifPresent(device -> {
      if (Boolean.FALSE.equals(device.getOnline())) {
        notifyOffline(session, deviceId, userId);
        return;
      }
      notifyLockStatus(session, device.getDisabled());
    });
//    echoBeat(session);
  }

  private Optional<LoginUser> getLoginUser(Session session) {
    final String token = session.getPathParameters().get(TOKEN);
    return localCache.get(token);
  }

  private void notifyOffline(Session session, Integer deviceId, Integer userId) {
    try {
      deviceMapper.findByIdAndUserId(deviceId, userId).ifPresent(device -> {
        device.setOnline(false);
        deviceMapper.updateByPrimaryKey(device);
      });
      sendMessage(session,
          DeviceMessageEnum.genDeviceMessage(DeviceMessageEnum.OFFLINE, new HashMap<>()));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


  private void notifyLockStatus(Session session,
      Boolean lockStatus) {
    try {
      sendMessage(session, DeviceMessageEnum.genDeviceMessage(DeviceMessageEnum.DISABLED,
          MapBuilder.create(new HashMap<String, Object>())
              .put("lockStatus", lockStatus).map()));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void notifyLockStatus(Integer userId, Integer deviceId, Boolean status) {
    Optional<Session> session = sessionHolder.getSession(userId, deviceId);
    session.ifPresent(s -> notifyLockStatus(s, status));
  }

  /**
   * 向各端同步 设备的推送状态
   *
   * @param userId    用户id
   * @param deviceId  设备id
   * @param pushState 消息推送开关状态
   */
  public void notifyPushState(Integer userId, Integer deviceId, Boolean pushState) {
    List<Session> sessions = sessionHolder.getOtherSession(userId);
    for (Session s : sessions) {
      try {
        sendMessage(s, DeviceMessageEnum.genDeviceMessage(DeviceMessageEnum.PUSH_STATE_CHANGE,
            MapBuilder.create(new HashMap<String, Object>())
                .put(DEVICE_ID, deviceId)
                .put("pushState", pushState).map()));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }


  /**
   * 通知客户端下线
   *
   * @param userId   用户id
   * @param deviceId 设备id
   */
  public void notifyOffline(Integer userId, Integer deviceId) {
    Optional<Session> session = sessionHolder.getSession(userId, deviceId);
    session.ifPresent(s -> notifyOffline(s, deviceId, userId));

  }

  public void removeSession(Session session) {
    Optional<LoginUser> loginUser = getLoginUser(session);
    if (!loginUser.isPresent()) {
      return;
    }
    removeSession(loginUser.get().getUserId(), loginUser.get().getDeviceId());

  }

  public void removeSession(Integer userId, Integer deviceId) {
    sessionHolder.remove(userId, deviceId);
  }


  private void echoBeat(Session session) {
    try {
      sendMessage(session, JsonReturn.ope().setMsg(DeviceMessageEnum.BEAT.name()).toJson());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


}
